#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <openssl/md5.h>
#include <errno.h>

#define PORT 8080
#define BUFFER_SIZE 1024
#define BLOCK_SIZE 4096

// 计算文件的MD5值
void calculateMD5(const char* filename, char* md5Str) {
    int fd;
    unsigned char data[BLOCK_SIZE];
    unsigned char md5Digest[MD5_DIGEST_LENGTH];
    MD5_CTX md5Context;
    ssize_t bytesRead;
    
    fd = open(filename, O_RDONLY);
    if (fd < 0) {
        perror("无法打开文件计算MD5");
        strcpy(md5Str, "");
        return;
    }
    
    MD5_Init(&md5Context);
    while ((bytesRead = read(fd, data, BLOCK_SIZE)) > 0) {
        MD5_Update(&md5Context, data, bytesRead);
    }
    MD5_Final(md5Digest, &md5Context);
    close(fd);
    
    // 转换为字符串
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
        sprintf(&md5Str[i*2], "%02x", (unsigned int)md5Digest[i]);
    }
}

int main(int argc, char const *argv[]) {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};
    
    // 检查参数
    if (argc != 2) {
        printf("用法: %s 服务器IP地址\n", argv[0]);
        return -1;
    }
    
    // 创建socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\nSocket创建失败\n");
        return -1;
    }
    
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    
    // 转换IP地址
    if (inet_pton(AF_INET, argv[1], &serv_addr.sin_addr) <= 0) {
        printf("\n无效的地址/地址不支持\n");
        return -1;
    }
    
    // 连接服务器
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\n连接失败\n");
        return -1;
    }
    
    // 用户登录
    char username[50], password[50];
    printf("请输入用户名: ");
    fgets(username, sizeof(username), stdin);
    username[strcspn(username, "\n")] = 0; // 移除换行符
    
    printf("请输入密码: ");
    fgets(password, sizeof(password), stdin);
    password[strcspn(password, "\n")] = 0; // 移除换行符
    
    // 发送登录信息
    snprintf(buffer, BUFFER_SIZE, "%s %s", username, password);
    send(sock, buffer, strlen(buffer), 0);
    
    // 接收登录结果
    memset(buffer, 0, BUFFER_SIZE);
    ssize_t valread = read(sock, buffer, BUFFER_SIZE);
    if (valread <= 0) {
        perror("读取登录结果失败");
        close(sock);
        return -1;
    }
    
    if (strncmp(buffer, "登录失败", 6) == 0) {
        printf("服务器响应: %s\n", buffer);
        close(sock);
        return -1;
    }
    
    printf("登录成功\n");
    
    // 请求下载文件
    char filename[256];
    printf("请输入要下载的文件名: ");
    fgets(filename, sizeof(filename), stdin);
    filename[strcspn(filename, "\n")] = 0; // 移除换行符
    
    send(sock, filename, strlen(filename), 0);
    
    // 接收文件信息
    memset(buffer, 0, BUFFER_SIZE);
    valread = read(sock, buffer, BUFFER_SIZE);
    if (valread <= 0) {
        perror("读取文件信息失败");
        close(sock);
        return -1;
    }
    
    // 检查文件是否存在
    if (strcmp(buffer, "文件不存在") == 0) {
        printf("服务器响应: %s\n", buffer);
        close(sock);
        return -1;
    }
    
    // 解析文件信息
    char serverFilename[256], serverMD5[33];
    off_t fileSize;
    if (sscanf(buffer, "%s %lld %s", serverFilename, &fileSize, serverMD5) != 3) {
        printf("解析文件信息失败\n");
        close(sock);
        return -1;
    }
    
    printf("文件信息:\n");
    printf("文件名: %s\n", serverFilename);
    printf("文件大小: %lld 字节\n", (long long)fileSize);
    printf("服务器MD5: %s\n", serverMD5);
    
    // 准备接收文件
    send(sock, "准备接收", 8, 0);
    
    // 创建本地文件
    char localFilename[256];
    snprintf(localFilename, sizeof(localFilename), "download_%s", serverFilename);
    int file_fd = open(localFilename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (file_fd < 0) {
        perror("创建文件失败");
        close(sock);
        return -1;
    }
    
    // 循环请求文件块
    off_t offset = 0;
    char blockBuffer[BLOCK_SIZE];
    printf("开始下载...\n");
    
    while (offset < fileSize) {
        // 请求特定偏移量的块
        snprintf(buffer, BUFFER_SIZE, "块 %lld", (long long)offset);
        send(sock, buffer, strlen(buffer), 0);
        
        // 接收块大小
        memset(buffer, 0, BUFFER_SIZE);
        valread = read(sock, buffer, BUFFER_SIZE);
        if (valread <= 0) {
            perror("读取块大小失败");
            break;
        }
        
        // 解析块大小
        ssize_t blockSize;
        if (sscanf(buffer, "大小 %zd", &blockSize) != 1) {
            printf("解析块大小失败: %s\n", buffer);
            break;
        }
        
        if (blockSize <= 0) {
            break;
        }
        
        // 接收块数据
        valread = read(sock, blockBuffer, blockSize);
        if (valread != blockSize) {
            perror("读取块数据失败");
            break;
        }
        
        // 写入文件
        if (write(file_fd, blockBuffer, blockSize) != blockSize) {
            perror("写入文件失败");
            break;
        }
        
        offset += blockSize;
        
        // 显示进度
        int progress = (int)((double)offset / fileSize * 100);
        printf("\r下载进度: %d%%", progress);
        fflush(stdout);
    }
    
    printf("\n下载完成\n");
    close(file_fd);
    
    // 通知服务器传输完成
    send(sock, "完成", 2, 0);
    
    // 计算本地文件的MD5并校验
    char localMD5[33] = {0};
    calculateMD5(localFilename, localMD5);
    printf("本地文件MD5: %s\n", localMD5);
    
    if (strcmp(localMD5, serverMD5) == 0) {
        printf("MD5校验成功，文件完整\n");
    } else {
        printf("MD5校验失败，文件可能损坏\n");
    }
    
    close(sock);
    return 0;
}
